package ar.com.seminario.common.service.localization;

import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

import ar.com.seminario.common.service.exception.BeanLocatorException;

/**
 * Implementación de un localizador de beans usando Spring.
 * 
 * @author Barbalarga & Camarro & Costa Zini & Lamas & Lamas
 */
@Component("beanLocator")
public class SpringBeanLocator implements IBeanLocator {

	/**
	 * Contexto de la aplicación a utilizar.
	 */
	@Autowired
	private ApplicationContext applicationContext;

	/**
	 * Obtiene un bean del registro. Para ello, usa el ID del mismo, así como la clase del bean a
	 * obtener.
	 * 
	 * @param <Bean> Tipo de clase de bean a obtener.
	 * @param beanId Identificador del bean (id del bean en Spring).
	 * @param beanInterface Clase del bean a obtener.
	 * @return Bean obtenido.
	 */
	public final <Bean> Bean getBean(final String beanId, final Class<Bean> beanInterface) {

		this.checkContext();

		try {
			return (this.getApplicationContext().getBean(beanId, beanInterface));
		} catch (final RuntimeException e) {
			final String serviceInterfaceName =
			        (beanInterface != null) ? beanInterface.getName() : "-null-";

			throw new BeanLocatorException("Error obteniendo el bean mediante su ID [" + beanId
			        + "] para la clase [" + serviceInterfaceName + "].", e);
		}
	}

	/**
	 * Método para obtener un bean que tiene una única implementación en el registro. Es importante
	 * destacar que sólo UN punto de bean debe implementar el mismo.
	 * 
	 * @param <Bean> Tipo de clase de bean a obtener.
	 * @param beanInterface La clase a la que debe castear el bean obtenido.
	 * @return El bean obtenido.
	 */
	@SuppressWarnings("unchecked")
	public final <Bean> Bean getBean(final Class<Bean> beanInterface) {

		this.checkContext();

		final String beanInterfaceName =
		        (beanInterface != null) ? beanInterface.getName() : "-null-";

		Map<String, Bean> beans = null;

		try {
			beans = this.getApplicationContext().getBeansOfType(beanInterface);
		} catch (final RuntimeException e) {
			throw new BeanLocatorException("Error obteniendo el bean mediante su clase ["
			        + beanInterfaceName + "].", e);
		}

		if (beans.size() > 1) {
			throw new BeanLocatorException("Hay mas de un bean implementado la interfaz indicada ["
			        + beanInterfaceName + "]. Intente obtenerlo por Bean Id.");
		} else if (beans.isEmpty()) {
			throw new BeanLocatorException("No hay bean que implemente la interfaz indicada ["
			        + beanInterfaceName + "].");
		} else {
			final Object[] array = beans.values().toArray();

			return ((Bean) array[0]);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see ar.com.conexia.common.service.localization.IBeanLocator#getBean(java.lang.String,
	 * java.lang.Object[])
	 */
	public Object getBean(final String beanId, final Object... args) {

		this.checkContext();

		try {
			return (this.getApplicationContext().getBean(beanId, args));
		} catch (final RuntimeException e) {

			throw new BeanLocatorException("Error obteniendo el bean mediante su ID [" + beanId
			        + "]", e);
		}
	}

	/**
	 * Asigna el Application Context.
	 * 
	 * @param applicationContext Application Context a asignar.
	 */
	public final void setApplicationContext(final ApplicationContext applicationContext) {

		this.applicationContext = applicationContext;
	}

	/**
	 * Devuelve el Application Context.
	 * 
	 * @return Application Context.
	 */
	public final ApplicationContext getApplicationContext() {

		return (this.applicationContext);
	}

	/**
	 * Verifica que el contexto de la aplicación haya sido inyectado al localizador. Lanza una
	 * excepción en caso contrario.
	 */
	private void checkContext() {

		if (this.getApplicationContext() == null) {
			throw new IllegalStateException("El Application Context no ha sido creado/asignado "
			        + "al localizador de beans de Spring.");
		}
	}

}
